package com.qianfeng.smsplatform.apigateway.filters;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.qianfeng.smsplatform.apigateway.cache.GrayReleaseConfigLocalCache;
import com.qianfeng.smsplatform.apigateway.pojo.GrayReleaseConfig;
import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Jackiechan on 2021/11/21/下午4:26
 *
 * @author Jackiechan
 * @version 1.0
 * 人学始知道，不学非自然。
 * @since 1.0
 */
@Component
public class GrayReleaseFilter extends ZuulFilter {

    Map<String, List<String>> serviceIdUrlForward = new ConcurrentHashMap<>();//保存每个服务和发地址权重的map

    private GrayReleaseConfigLocalCache grayReleaseConfigLocalCache;

    @Autowired
    public void setGrayReleaseConfigLocalCache(GrayReleaseConfigLocalCache grayReleaseConfigLocalCache) {
        this.grayReleaseConfigLocalCache = grayReleaseConfigLocalCache;
    }

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return FilterConstants.PRE_DECORATION_FILTER_ORDER + 200;
    }

    @Override
    public boolean shouldFilter() {
        //跨域或者是携带token等信息的时候会先发起一次option请求
        RequestContext requestContext = RequestContext.getCurrentContext();
        boolean should = requestContext.sendZuulResponse();//前面有没有拦截
        HttpServletRequest request = requestContext.getRequest();
        boolean isOPtion = request.getMethod().equals(RequestMethod.OPTIONS.name());
        if (isOPtion) {
            System.err.println("OPTIONS请求不做拦截操作");
            return false;
        }
        //如果不是需要灰度发布的服务也不需要
        Map<String, List<GrayReleaseConfig>> releaseConfigMap = grayReleaseConfigLocalCache.getGrayReleaseConfigMap();
        Object serviceId = requestContext.get(FilterConstants.SERVICE_ID_KEY);//访问的服务
        Object url = requestContext.get(FilterConstants.REQUEST_URI_KEY);//访问路径
        if (ObjectUtils.isEmpty(serviceId)) {
            return false;
        } else {
            List<GrayReleaseConfig> configs = releaseConfigMap.get(serviceId.toString());//获取当前服务的所有灰度，但是不一定针对当前地址
            List<GrayReleaseConfig> currentUrlConfig = null;//保存匹配当前地址的灰度，保存后后面使用
            if (configs == null || configs.size() == 0) {//没有配置灰度设置
                return false;//不需要灰度发布
            }
            boolean isNeed = false;
            for (GrayReleaseConfig config : configs) {
                //如果地址也不是灰度地址 就拦截
                String path = config.getPath();
                if (ObjectUtils.isEmpty(url)) {//没有配置地址,自己定义规则，如没有地址则默认所有地址
//                return false;
                    isNeed = true;
                } else {
                    String[] paths = path.split("/");//灰度的地址，因为可能是path参数，所以需要判定是不是一个地址，判定的方式就是相同的长度，不是{}位置的内容一致
                    String[] urls = url.toString().split("/");//裁剪请求路径
                    if (paths.length != urls.length) {
                        return false;//长度不一致代表不是一个路径
                    } else {
                        for (int i = 0; i < urls.length; i++) {
                            String currentUrl = urls[i];//获取请求地址的每个位置部分
                            String currentPath = paths[i];//获取定义的地址的当前部分.
                            //如果当前位置currentPath不是{}开头并且和currentUrl不相等，代表不一致
                            if (!currentPath.contains("{") && !currentPath.equalsIgnoreCase(currentUrl)) {
                                return false;
                            }
                            if (currentUrlConfig == null) {
                                currentUrlConfig = new ArrayList<>();
                            }
                            currentUrlConfig.add(config);//当前的灰度匹配当前地址
                        }
                    }
                }
            }
            if (should && isNeed) {
                requestContext.set("grayreleaseconfig", currentUrlConfig);//缓存配置，在下面的run中使用
            }
        }

        return should;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        List<GrayReleaseConfig> grayReleaseConfigList = (List<GrayReleaseConfig>) currentContext.get("grayreleaseconfig");
        //根据我们定义的规则进行区分，比如可以要求传递某个参数来区分转发到那个服务
//        String forp = currentContext.getRequest().getParameter("forp");
//        if (!ObjectUtils.isEmpty(forp)) {
//            RibbonFilterContextHolder.getCurrentContext().add("forward", forp);//这句话就代表将请求路由到metadata-map里forward为2的那个服务
//        }else{
//
//        }
        //少数情况我们一个服务会有多种不同的灰度设置，那样上面我们获取到的config会是一个集合，我们到时候按照要求进行区分即可，比如10%到a 30%到b，具体去实现即可
        //如果纯粹是为了权重,没有其他的判断准则，可以直接按照权重走,可以保存对应数量的数据到一个集合中，每次从里面弹出一个数据
        //比如a版本10，b版本30，c版本60 ,保存对应个个数的forward值到集合中，并打乱
        //还有其他的方式，不管什么方式都是按照业务的灰度规则

        GrayReleaseConfig config = grayReleaseConfigList.get(0);
        List<String> forwardList = serviceIdUrlForward.get(config.getServiceId() + config.getPath());//获取缓存的当前地址的权重
        if (forwardList == null || forwardList.size() == 0) {
            forwardList = new ArrayList<>();
            serviceIdUrlForward.put(config.getServiceId() + config.getPath(), forwardList);
            for (GrayReleaseConfig grayReleaseConfig : grayReleaseConfigList) {
                String forward = grayReleaseConfig.getForward();
                for (int i = 0; i < Integer.parseInt(forward); i++) {
                    forwardList.add(forward);//保存对应个数的服务标识
                }
            }
            Collections.shuffle(forwardList);//打乱顺序
        }
        String forwardString = forwardList.remove(0);//总是从头部移除一个数据作为转发的标识
        RibbonFilterContextHolder.getCurrentContext().add("forward", forwardString);//这句话就代表将请求路由到metadata-map里forward为2的那个服务
        return null;
    }
}
